#include <master.h>
#include <keyb.h>

/* sekiOS
 *
 * keyb.c - keyboard stuff
 *
 */

unsigned char *kbdhead, *kbdbuf;	// keyboard buffer
const unsigned int kbd_buf_limit = 1024;            // safety


unsigned char kbdus[] = {
  0, 27, '1', '2', '3', '4', '5', '6',
  '7', '8', '9', '0', '-', '=', '\b', '\t',
  'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
  'o', 'p', '[', ']', '\n', 0, 'a', 's',
  'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
  '\'', '`', 0, '\\', 'z', 'x', 'c', 'v',
  'b', 'n', 'm', ',', '.', '/', 0, 0,
  0, ' ', 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
  KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, 0, 0, KEY_HOME,
  KEY_UP, KEY_PGUP, '-', KEY_LFT, '5', KEY_RT, '+', KEY_END,
  KEY_DN, KEY_PGDN, KEY_INS, KEY_DEL, 0, 0, 0, KEY_F11,
  KEY_F12
};


static void pushkbdc (unsigned char c)
{
 putch(c);
 kbdbuf++;
 if ((kbdbuf-kbdhead) > kbd_buf_limit) {
   // xxx we should beep here
   kbdbuf--;
   dprint("keyboard overflow!\n");
   return;
 }
 *kbdbuf = c;
}

unsigned char
popkbdc (void)
{
 unsigned char ret = *kbdbuf;
 kbdbuf--;
 return ret;
}


/* Write to keyboard */
static void writekbd (unsigned char port, unsigned char data)
{
  unsigned long timeout = 500000L;
  unsigned char stat;

  while (timeout)
    {
      stat = inportb (KBD_PORT);
      if ((stat & 0x02) == 0)
	break;
      timeout--;
    }
  if (timeout != 0)
    outportb (port, data);
}

/* Read from keyboard */
static unsigned char readkbd ()
{
  unsigned long timeout = 500000L;
  unsigned char stat, data;

  while (timeout)
    {
      stat = inportb (KBD_PORT);
      if ((stat & 0x01) != 0)
	{
	  data = inportb (KBD_DATA);
	  if ((stat & 0xC0) == 0)
	    return data;
	}
      timeout--;
    }
  return 0;
}

/* Read and Write from keyboard */
static unsigned char
readwritekbd (unsigned char port, unsigned char data, const char *expected)
{
  unsigned char result;
  writekbd (port, data);
  while (*expected)
    {
      result = readkbd ();
      if (*expected != result)
	return result;
      expected++;
    }
  return 0;
}

/* Sets the keyboard LEDs */
static void keyboard_leds (unsigned short status)
{
  unsigned short temp = 0;
  writekbd (KBD_DATA, KBD_SET_LEDS);
  if (status & KBD_SCRL)
    temp |= 1;
  if (status & KBD_NUM)
    temp |= 2;
  if (status & KBD_CAPS)
    temp |= 4;
  writekbd (KBD_DATA, temp);	/* Bottom 3 bits set LEDs */
}

/* Handles the keyboard interrupt */
void keyboard_handler (struct regs *r)
{
  static unsigned short status;
  unsigned char scancode, key = 0;

  /* Read from the keyboard's data buffer */
  scancode = inportb (KBD_DATA);

  // TODO: Sync status and LEDs with keyboard

  /* If the top bit of the byte we read from the keyboard is
   * set, that means that a key has just been released */
  if (scancode & 0x80)
    {
      scancode &= 0x7F;		// take the high bit out
      switch (scancode)
	{
	  // switch for state keys (Ctrl, Alt, Shift)
	case KEY_LCTRL:	// KEY_RCTRL
	  status &= ~KBD_CTRL;
	  break;

	case KEY_LALT:		// KEY_RALT
	  status &= ~KBD_ALT;
	  break;

	case KEY_LSHIFT:	// KEY_RSHIFT
	  status &= ~KBD_SHIFT;
	  break;
	}

    }
  else
    {
      switch (scancode)
	{
	case KEY_LCTRL:	// KEY_RCTRL
	  status |= KBD_CTRL;
	  break;

	case KEY_LALT:		// KEY_RALT
	  status |= KBD_ALT;
	  break;

	case KEY_LSHIFT:	// KEY_RSHIFT
	  status |= KBD_SHIFT;
	  break;

	case KEY_SCRL:
	  status ^= KBD_SCRL;
	  keyboard_leds (status);
	  break;

	case KEY_NUM:
	  status ^= KBD_NUM;
	  keyboard_leds (status);
	  break;

	case KEY_CAPS:
	  status ^= KBD_CAPS;
	  keyboard_leds (status);
	  break;
	}

      /* Ignore invalid scan codes and Convert raw scancode to ASCII key */
      if (scancode >= (sizeof (kbdus) / sizeof (kbdus[0])))
	key = 0;
      else if ((status & KBD_CAPS) || (status & KBD_SHIFT))
	key = toupper (kbdus[scancode]);
      else
	key = kbdus[scancode];

      /* Reboot if we have Ctrl+Alt+Del */
      if ((status & KBD_CTRL) && (status & KBD_ALT) && (key == KEY_DEL))
	reboot ();

      /* If we have a valid key process it */
      if (key != 0)
	{
	  /* Push key in to the keyboard buffer */
	  pushkbdc (key);
	}
    }
}

/* Installs the keyboard handler into IRQ1 */
void
kbd_install ()
{
  kbdbuf = kbdhead = kmalloc(kbd_buf_limit);
  memset(kbdbuf, 0 , kbd_buf_limit);
  irq_install_handler (1, keyboard_handler);
}

